/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <hal_part.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static hal_logic_part_t *s_lpar = NULL;
static uint32_t s_lpar_num = 0;
static hal_physic_dev_t *s_pdev = NULL;
static uint32_t s_pdev_num = 0;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/**
 * __get_physic_dev - get physical device by the partition id
 * @part_id: partition id
 * @offset:  offset value based on 0
 * @len:     length of data
 * @pos:     the position address (start_addr + offset)
 *
 * return: a pointer to the phys dev struct or NULL if param invalid
 */
static hal_physic_dev_t *__get_physic_dev(uint32_t part_id, size_t offset, size_t len, size_t *pos)
{
    uint32_t i;
    hal_logic_part_t *lp = NULL;
    hal_physic_dev_t *pd = NULL;

    for (i = 0; i < s_lpar_num; i++) {
        lp = s_lpar + i;
        if (lp->part_id == part_id) {
            break;
        }
    }
    if (i >= s_lpar_num) {
        return NULL;
    }
    if (offset + len > lp->size) {
        return NULL;
    }
    for (i = 0; i < s_pdev_num; i++) {
        pd = s_pdev + i;
        if (pd->dev_id == lp->dev_id) {
            break;
        }
    }
    if (i >= s_pdev_num) {
        return NULL;
    }
    *pos = lp->start_addr + offset;

    return pd;
}

int hal_part_erase(uint32_t part_id, size_t offset, size_t len)
{
    size_t pos;
    hal_physic_dev_t *pd = __get_physic_dev(part_id, offset, len, &pos);

    if (pd == NULL) {
        return -EINVAL;
    }

    return hal_mtd_erase(pd->mtd, pos, len);
}

int hal_part_read(uint32_t part_id, size_t offset, size_t len, void *buf)
{
    size_t pos;
    hal_physic_dev_t *pd = __get_physic_dev(part_id, offset, len, &pos);

    if (buf == NULL || pd == NULL) {
        return -EINVAL;
    }

    return hal_mtd_read(pd->mtd, pos, len, buf);
}

int hal_part_write(uint32_t part_id, size_t offset, size_t len, const void *buf)
{
    size_t pos;
    hal_physic_dev_t *pd = __get_physic_dev(part_id, offset, len, &pos);

    if (buf == NULL || pd == NULL) {
        return -EINVAL;
    }

    return hal_mtd_write(pd->mtd, pos, len, buf);
}

/**
 * hal_part_write_safe - Safely write data to a partition
 * @part_id: Partition ID
 * @offset:  Offset within the partition (based on 0)
 * @len:     Length of data to write
 * @buf:     Pointer to the data buffer
 *
 * Return: 0 on success, negative value on error
 */
int hal_part_write_safe(uint32_t part_id, size_t offset, size_t len, const void *buf)
{
    size_t pos;
    hal_physic_dev_t *pd = __get_physic_dev(part_id, offset, len, &pos);

    if (buf == NULL || pd == NULL) {
        return -EINVAL;
    }

    return hal_mtd_write_safe(pd->mtd, pos, len, buf);
}

int hal_part_erase_write(uint32_t part_id, size_t offset, size_t len, const void *buf)
{
    size_t pos;
    hal_physic_dev_t *pd = __get_physic_dev(part_id, offset, len, &pos);

    if (buf == NULL || pd == NULL) {
        return -EINVAL;
    }

    return hal_mtd_erase_write(pd->mtd, pos, len, buf);
}

static hal_logic_part_t *__get_logic_part(uint32_t part_id)
{
    uint32_t i;
    hal_logic_part_t *lp = NULL;

    for (i = 0; i < s_lpar_num; i++) {
        lp = s_lpar + i;
        if (lp->part_id == part_id) {
            return lp;
        }
    }
    return NULL;
}

int hal_part_get_start_addr(uint32_t part_id, size_t *addr)
{
    hal_logic_part_t *lp = __get_logic_part(part_id);

    if (lp == NULL || addr == NULL) {
        return -EINVAL;
    }
    *addr = lp->start_addr;
    return 0;
}

int hal_part_get_size(uint32_t part_id, size_t *size)
{
    hal_logic_part_t *lp = __get_logic_part(part_id);

    if (lp == NULL || size == NULL) {
        return -EINVAL;
    }
    *size = lp->size;
    return 0;
}

/**
 * hal_part_register - Register logical partitions and physical devices
 * @part:     Array of logical partitions
 * @part_cnt: Number of logical partitions
 * @dev:      Array of physical devices
 * @dev_cnt:  Number of physical devices
 *
 * This function initializes the logical partitions and physical devices
 * by associating them with their respective memory technology devices (MTDs).
 *
 * @code
 *  enum LPAR_ID {
 *      LPAR_ID_LOADER,
 *      LPAR_ID_APP,
 *      LPAR_ID_FLAG,
 *      LPAR_ID_DOWNLOAD,
 *      LPAR_ID_BACKUP,
 *  };
 *
 *  enum PDEV_ID {
 *      PDEV_ID_INNER_FLASH,
 *      PDEV_ID_SPI_FLASH,
 *  };
 *
 *  static hal_logic_part_t test_part[] = {
 *      {LPAR_ID_LOADER,   PDEV_ID_INNER_FLASH, "loader",   0x08000000, 0x00020000},
 *      {LPAR_ID_APP,      PDEV_ID_INNER_FLASH, "app",      0x08020000, 0x000E0000},
 *      {LPAR_ID_FLAG,     PDEV_ID_SPI_FLASH,   "flag",     0x00000000, 0x00004000},
 *      {LPAR_ID_DOWNLOAD, PDEV_ID_SPI_FLASH,   "download", 0x00004000, 0x00080000},
 *      {LPAR_ID_BACKUP,   PDEV_ID_SPI_FLASH,   "backup",   0x00084000, 0x00080000},
 *  };
 *
 *  static hal_physic_dev_t test_dev[] = {
 *      {PDEV_ID_INNER_FLASH, "flash",  NULL},
 *      {PDEV_ID_SPI_FLASH,   "w25x",   NULL},
 *  };
 *
 * hal_part_register(test_part, ARRAY_SIZE(test_part), test_dev, ARRAY_SIZE(test_dev));
 * @endcode
 *
 * Return: 0 on success, negative value on error
 */
int hal_part_register(hal_logic_part_t part[], uint32_t part_cnt, hal_physic_dev_t dev[],
                      uint32_t dev_cnt)
{
    uint32_t i;

    if (part == NULL || dev == NULL) {
        return -EINVAL;
    }

    for (i = 0; i < dev_cnt; ++i) {
        dev[i].mtd = hal_mtd_open(dev[i].name);
        if (dev[i].mtd == NULL) {
            return -EPERM;
        }
        hal_mtd_ioctl(dev[i].mtd, HAL_MTD_CMD_UNLOCK);
    }

    s_lpar = part;
    s_lpar_num = part_cnt;
    s_pdev = dev;
    s_pdev_num = dev_cnt;

    return 0;
}
